#include "openmove_Prefix.h"
#include "NetBase.h"
#include "NetClient.h"
#include "NetServer.h"
#include "hawkthreads.h"
#include "OgreEngine.h"
#include "SceneBase.h"

#include <CEGUI/elements/CEGUISlider.h>
#include <CEGUI/elements/CEGUIStaticText.h>
#include <CEGUI/elements/CEGUIScrollbar.h>
#include <CEGUI/elements/CEGUIComboBox.h>

CNetClient * CNetClient::ms_pSingleton = NULL;

CNetClient :: CNetClient(COgreEngine *pEngine, char const * const pAddress, int const pPort) : CNetBase(pEngine), m_IsAddressSet(false), m_LastPacket(-1)
{
	SetAddress(pAddress, pPort);

	assert(NULL == ms_pSingleton);
	ms_pSingleton = this;
}

CNetClient :: ~CNetClient(void)
{
}

void CNetClient :: Destroy(void)
{
	CNetBase::Destroy();
}

void CNetClient :: Create(void)
{
	CNetBase::Create();

#ifdef __USE_HAWKTHREAD__
	htThreadCreate(CNetClient::MainClientLoop, this, HT_TRUE);
#endif
#ifdef __USE_WIN32THREAD__
	HANDLE thread = CreateThread(NULL, 0, CNetClient::MainClientLoop, this, 0, NULL);
	DWORD mask = 4;
	SetThreadAffinityMask(thread, mask);
#endif
	m_IsReady = true;
}

static CEGUI::StaticText *selectionText = NULL;
bool CNetClient :: frameStarted(const FrameEvent& evt)
{
	if (NULL == selectionText)
	{
		CEGUI::WindowManager &wmgr = CEGUI::WindowManager::getSingleton();
		selectionText = (CEGUI::StaticText*)wmgr.createWindow("TaharezLook/StaticText", (CEGUI::utf8*)String("NetMessage").c_str());
		selectionText->setPosition(CEGUI::Point(0.8f, 0.5f));
		selectionText->setSize(CEGUI::Size(0.2f, 0.1f));
		selectionText->setText("");
	}

	if (NULL != selectionText)
	{
		//selectionText->setText("seed: " + StringConverter::toString(m_LastPacket.m_FrameNumber));
		selectionText->setText("netanimframe: " + StringConverter::toString(m_LastPacket.m_AnimationsFrame) + "\n" +
			"nettime: " + StringConverter::toString(m_LastPacket.m_Time / 1000.0f));
		selectionText->setText(selectionText->getText() + "\nserverReady: " + StringConverter::toString(m_LastPacket.m_IsReady));
		//selectionText->setText(selectionText->getText() + "\nanim0: " + StringConverter::toString(m_LastPacket.m_animPacket[0]));
		//selectionText->setText(selectionText->getText() + "\nanim0: " + StringConverter::toString(m_pEngine->m_pCurrentScene->m_animPacket[0]));
		selectionText->setText(selectionText->getText() + "\nnetcam: " + StringConverter::toString(m_LastPacket.m_Camera.m_Position));
		selectionText->setText(selectionText->getText() + "\nnetmovie6: " + StringConverter::toString(m_LastPacket.m_moviesFrame[6]));
	}

	m_pEngine->m_pCurrentScene->m_GuiSheet->addChildWindow(selectionText);
	//if (0 == m_LastPacket.m_FrameNumber % 300 && NULL != selectionText)


#ifdef __USE_HAWKTHREAD__
	htMutexLock(&m_QueueReadMutex);
#endif
#ifdef __USE_WIN32THREAD__
	EnterCriticalSection(&m_QueueReadSection);
#endif

	if (m_pEngine && m_pEngine->m_pCurrentScene && -1 != m_NetID)
	{
		if (m_pEngine->m_pCurrentScene->m_ScenePos == m_LastPacket.m_SceneId)
		{
			SShared command(m_NetID);
			command.m_Command = CMD_SETREADY;
			while (!SendPacket(m_Socket, &command, sizeof(SShared)))
			{
#			ifdef __USE_HAWKTHREAD__
				//htThreadSleep(0);
				htThreadYield();
#			endif
#			ifdef __USE_WIN32THREAD__
				Sleep(0);
#			endif
			}
		}
		else
		{
			//m_pEngine->m_Tasks.push_back(STask(CMD_SETSCENE, m_LastPacket.m_SceneId));
		}
	}

	unsigned int const nbBefore = static_cast<unsigned int>(m_CommandQueue.size());
	for (unsigned int i = 0; i < m_CommandQueue.size(); ++i)
	{
		SShared &Command = m_CommandQueue.at(0);
		if (Command.m_Command == CMD_START)
		{
			if (NULL != m_pEngine)
			{
				//m_pEngine->m_Root.setCurrentFrameNumber(Command.m_FrameNumber);
			}
		}
		else
		{
			//if (Command.m_FrameNumber <= m_pEngine->m_Root.getCurrentFrameNumber())
			{
				switch (Command.m_Command)
				{
					case CMD_STARTANIM:
					{
						if (NULL != m_pEngine && NULL != m_pEngine->m_pCurrentScene)
						{
							m_pEngine->m_pCurrentScene->Reset();
						}

						break;
					}
					case CMD_EVENT:
					{
						if (NULL != m_pEngine && NULL != m_pEngine->m_pCurrentScene)
						{
							m_pEngine->m_pCurrentScene->addEvent(Command.m_OSCPacket, Command.m_Time);
						}
						break;
					}
					case CMD_NEXTSCENE:
					{
						if (NULL != m_pEngine)
						{
								//m_pEngine->startNextScene();
							m_pEngine->m_Tasks.push_back(CMD_NEXTSCENE);
							m_IsServerReady = false;
						}
						break;
					}
					case CMD_PREVSCENE:
					{
						if (NULL != m_pEngine)
						{
							//m_pEngine->startPreviousScene();
							m_pEngine->m_Tasks.push_back(CMD_PREVSCENE);
							m_IsServerReady = false;
						}
						break;
					}
					case CMD_SETSCENE:
					{
						if (NULL != m_pEngine)
						{
							//m_pEngine->startScene(Command.m_OSCPacket.sequence_id);
							m_pEngine->m_Tasks.push_back(STask(CMD_SETSCENE, Command.m_OSCPacket.sequence_id));
							m_IsServerReady = false;
							LogManager::getSingleton().logMessage("setscene " + StringConverter::toString(Command.m_OSCPacket.sequence_id));
						}
						break;
					}
					default:
					{
						break;
					}
				}
			}
		}
		m_CommandQueue.erase(m_CommandQueue.begin());
	}
	//m_pEngine->setDebugText("nb command: " + StringConverter::toString(m_CommandQueue.size()) + "/" + StringConverter::toString(nbBefore) + "\n");
	//m_pEngine->m_pCurrentRenderWindow->setDebugText("frame: " + StringConverter::toString(m_pEngine->m_pCurrentScene->GetAnimationsFrame()));


	
	if (NULL != m_pEngine && NULL != m_pEngine->m_pCurrentScene && m_IsServerReady)
	{
		//m_pEngine->m_pCurrentScene->SetCamera(m_LastPacket.m_Camera.m_Position, m_LastPacket.m_Camera.m_Orientation);
		//m_pEngine->m_pCurrentScene->SetAnimationsFrame(m_LastPacket.m_AnimationsFrame);
		srand(m_LastPacket.m_FrameNumber);
		Real curTime = m_pEngine->m_pCurrentScene->m_Timer.getMilliseconds();
		Real timeDiff = curTime - m_LastPacket.m_Time;
		if (timeDiff < -64.0f || timeDiff > 64.0f)
		{
			m_pEngine->m_pCurrentScene->m_Timer.setTime(m_LastPacket.m_Time);
			//selectionText->setText(selectionText->getText() + "\nsyncing");
		}

		m_pEngine->m_pCurrentScene->m_Timer.setTime(m_LastPacket.m_Time);
		CSyncManager::getSingleton().m_Timer.setTime(m_LastPacket.m_Time);
		CSyncManager::getSingleton().m_MovieFrame = m_LastPacket.m_MovieFrame;

		LogManager::getSingleton().logMessage(StringConverter::toString(m_pEngine->m_pCurrentScene->m_ScenePos) + " == " +
			StringConverter::toString(m_LastPacket.m_SceneId)
			);
		if (m_pEngine->m_pCurrentScene->m_ScenePos == m_LastPacket.m_SceneId)
		{
			htMutexLock(&CSyncManager::getSingleton().m_updateMutex);
			memcpy(m_pEngine->m_pCurrentScene->m_animPacket, m_LastPacket.m_animPacket, sizeof(m_LastPacket.m_animPacket));
			htMutexUnlock(&CSyncManager::getSingleton().m_updateMutex);
			LogManager::getSingleton().logMessage("copied");
		}

		LogManager::getSingleton().logMessage("ready " + StringConverter::toString(m_LastPacket.m_Time) + " " + StringConverter::toString(m_LastPacket.m_IsReady));
	}
	else
	{
		LogManager::getSingleton().logMessage("not ready");
	}

	htMutexLock(&CSyncManager::getSingleton().m_updateMutex);
	memcpy(CSyncManager::getSingleton().m_moviesFrame, m_LastPacket.m_moviesFrame, sizeof(m_LastPacket.m_moviesFrame));
	htMutexUnlock(&CSyncManager::getSingleton().m_updateMutex);
	

#ifdef __USE_HAWKTHREAD__
	htMutexUnlock(&m_QueueReadMutex);
#endif
#ifdef __USE_WIN32THREAD__
	LeaveCriticalSection(&m_QueueReadSection);
#endif
	if (!m_IsServerReady && 0)
	{
#ifdef __USE_HAWKTHREAD__
		htThreadYield();
#endif
#ifdef __USE_WIN32THREAD__
		Sleep(1);
#endif
	}

	if (!m_IsServerReady)
	{
		if (NULL != m_pEngine && NULL != m_pEngine->m_pCurrentScene)
		{
			//m_pEngine->m_pCurrentScene->Reset();
		}
	}

	return (CNetBase::frameStarted(evt));
}

#ifdef __USE_HAWKTHREAD__
	void * CNetClient :: MainClientLoop(void * pClient)
#endif
#ifdef __USE_WIN32THREAD__
	DWORD WINAPI CNetClient :: MainClientLoop( LPVOID pClient ) 
#endif
{
    NLint       count = 4;
	CNetClient *pNetClient = static_cast<CNetClient *>(pClient);

	pNetClient->TryConnect();

	/* create the client sockets */
	SShared Shared(-1);

	while (1)
	{
		int readlen = 0;

		if (pNetClient->m_Shutdown)
		{
			return (NULL);
		}

        while ((readlen = nlRead(pNetClient->m_Socket, &Shared, sizeof(Shared))) > 0)
        {
#ifdef __USE_HAWKTHREAD__
			htMutexLock(&pNetClient->m_QueueReadMutex);
#endif
#ifdef __USE_WIN32THREAD__
			EnterCriticalSection(&m_QueueReadSection);
#endif

			if (Shared.m_Command == CMD_CAMSET)
			{
				pNetClient->m_LastPacket = Shared;
				pNetClient->m_IsServerReady = Shared.m_IsReady;






/*

	if (NULL != pNetClient->m_pEngine && NULL != pNetClient->m_pEngine->m_pCurrentScene && pNetClient->m_IsServerReady)
	{
		//m_pEngine->m_pCurrentScene->SetCamera(m_LastPacket.m_Camera.m_Position, m_LastPacket.m_Camera.m_Orientation);
		//m_pEngine->m_pCurrentScene->SetAnimationsFrame(m_LastPacket.m_AnimationsFrame);
		srand(pNetClient->m_LastPacket.m_FrameNumber);
		Real curTime = pNetClient->m_pEngine->m_pCurrentScene->m_Timer.getMilliseconds();
		Real timeDiff = curTime - pNetClient->m_LastPacket.m_Time;
		if (timeDiff < -64.0f || timeDiff > 64.0f)
		{
			pNetClient->m_pEngine->m_pCurrentScene->m_Timer.setTime(pNetClient->m_LastPacket.m_Time);
			//selectionText->setText(selectionText->getText() + "\nsyncing");
		}

		pNetClient->m_pEngine->m_pCurrentScene->m_Timer.setTime(pNetClient->m_LastPacket.m_Time);
		CSyncManager::getSingleton().m_Timer.setTime(pNetClient->m_LastPacket.m_Time);
		CSyncManager::getSingleton().m_MovieFrame = pNetClient->m_LastPacket.m_MovieFrame;

		LogManager::getSingleton().logMessage(StringConverter::toString(pNetClient->m_pEngine->m_pCurrentScene->m_ScenePos) + " == " +
			StringConverter::toString(pNetClient->m_LastPacket.m_SceneId)
			);
		if (pNetClient->m_pEngine->m_pCurrentScene->m_ScenePos == pNetClient->m_LastPacket.m_SceneId && !pNetClient->m_LastPacket.m_IsMovie)
		{
			htMutexLock(&CSyncManager::getSingleton().m_updateMutex);
			memcpy(pNetClient->m_pEngine->m_pCurrentScene->m_animPacket, pNetClient->m_LastPacket.m_animPacket, sizeof(pNetClient->m_LastPacket.m_animPacket));
			htMutexUnlock(&CSyncManager::getSingleton().m_updateMutex);
			LogManager::getSingleton().logMessage("copied");
		}

		//LogManager::getSingleton().logMessage("ready " + StringConverter::toString(pNetClient->m_LastPacket.m_Time) + " " + StringConverter::toString(pNetClient->m_LastPacket.m_IsReady));
	}
	else
	{
		//LogManager::getSingleton().logMessage("not ready");
	}

	//if (pNetClient->m_LastPacket.m_IsMovie)
	{
		htMutexLock(&CSyncManager::getSingleton().m_updateMutex);
		memcpy(CSyncManager::getSingleton().m_moviesFrame, pNetClient->m_LastPacket.m_moviesFrame, sizeof(pNetClient->m_LastPacket.m_moviesFrame));
		htMutexUnlock(&CSyncManager::getSingleton().m_updateMutex);
	}

*/









			}
			else
			{
				if (Shared.m_Command == CMD_START)
				{
					pNetClient->m_NetID = Shared.m_NetId;
				}

				pNetClient->m_CommandQueue.push_back(Shared);
			}

			//CSyncManager::getSingleton().m_MovieFrame = pNetClient->m_LastPacket.m_MovieFrame;


#ifdef __USE_HAWKTHREAD__				
			htMutexUnlock(&pNetClient->m_QueueReadMutex);
#endif
#ifdef __USE_WIN32THREAD__
			LeaveCriticalSection(&m_QueueReadSection);
#endif
		}

		if (readlen == NL_INVALID)
        {
            NLenum err = nlGetError();

            if( err == NL_MESSAGE_END || err == NL_SOCK_DISCONNECT)
            {
				Ogre::LogManager::getSingleton().logMessage("CLIENT:socket closed");
				nlClose(pNetClient->m_Socket);
				pNetClient->m_Shutdown = true;
            }
			else
			{
				if (err == NL_CON_REFUSED)
				{
					Ogre::LogManager::getSingleton().logMessage("Connection refused");
					nlClose(pNetClient->m_Socket);
					pNetClient->TryConnect();
				}
			}
        }

#ifdef __USE_HAWKTHREAD__
		//htThreadSleep(0);
		htThreadYield();
#endif
#ifdef __USE_WIN32THREAD__
		Sleep(0);
#endif
	}

	pNetClient->m_Shutdown = true;
	Ogre::LogManager::getSingleton().logMessage("CLIENT CLOSED");

#ifdef __USE_HAWKTHREAD__
	return (void *)NULL;
#endif
#ifdef __USE_WIN32THREAD__
	return 0;
#endif
}

void CNetClient :: SetAddress(char const * const pAddress, int const pPort)
{
	if (NULL != pAddress)
	{
		char pPortString[1024];
		char pAddressString[1024];

		memset(pPortString, 0, sizeof(pPortString));
		memset(pAddressString, 0, sizeof(pAddressString));

		_itoa(pPort, pPortString, 10);
		char fullAddress[1024];
		snprintf(fullAddress, sizeof(fullAddress), "%s:%i", pAddress, pPort);

		nlStringToAddr(reinterpret_cast<const NLchar *>(fullAddress), &m_Address);
		m_IsAddressSet = true;
		m_AddressString = fullAddress;
	}
	else
	if (m_IsAddressSet == true)
	{
		nlStringToAddr(reinterpret_cast<const NLchar *>(m_AddressString.c_str()), &m_Address);
	}
}

void CNetClient :: TryConnect(void)
{
	SetAddress();
	m_Socket = nlOpen(0, NL_TCP_PACKETS);
	if (m_Socket == NL_INVALID)
	{
		OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "HawkNL: CLIENT: nlOpen error", "CNetClient::MainClientLoop");
	}

	if (NL_FALSE == nlConnect(m_Socket, &m_Address))
	{
		String msg = String(reinterpret_cast<char const *>(nlGetErrorStr(nlGetError()))) + String(reinterpret_cast<char const *>(nlGetSystemErrorStr(nlGetSystemError())));
		OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, msg, "CNetClient::MainClientLoop");
	}

	NLbyte		pMessage[256];
	NLbyte		pStr[256];
	sprintf(pMessage, "CLIENT connect to %s", nlAddrToString(&m_Address, reinterpret_cast<NLchar *>(pStr)));
	Ogre::LogManager::getSingleton().logMessage(pMessage);
}
